package com.crjjason.utility.redpacket;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.apache.commons.math3.distribution.BetaDistribution;

public class RedPacketDistribution {
	private RedPacketDistributionConfig config;
	private int totalAmount;
	private int numberOfRedPacket;
	private boolean distributed = false;
	private BetaDistribution betaDistribution = new BetaDistribution(2, 5);

	public RedPacketDistribution(int totalAmount, int numberOfRedPacket) {
		this(totalAmount, numberOfRedPacket, RedPacketDistributionConfig.getDefaultRedPacketDistributionConfig());
	}

	public RedPacketDistribution(int totalAmount, int numberOfRedPacket, RedPacketDistributionConfig config) {
		this.totalAmount = totalAmount;
		this.numberOfRedPacket = numberOfRedPacket;
		this.config = config;

		if (this.totalAmount < 0) {
			throw new IllegalArgumentException("totalAmount should be positive");
		}
		if (this.numberOfRedPacket < 0) {
			throw new IllegalArgumentException("numberOfRedPacket should be positive");
		}
		if(!isValidDistribution(totalAmount, numberOfRedPacket)){
			throw new IllegalArgumentException("It is impossible to complete the distribution");
		}
	}
	/**
	 * Get the red packets. This could only run once for each distribution
	 * @return
	 */
	public List<RedPacket> distributeRedPackets(){
		if(distributed){
			throw new IllegalStateException("The distribution has been executed once");
		}
		List<RedPacket> redPackets = new ArrayList<RedPacket>();
		int remainingAmount = totalAmount;
		for(int i = 0; i < numberOfRedPacket; i++){
			int amount = randomRedPacket(remainingAmount, config.getMinimum(), config.getMaximum(), numberOfRedPacket - i);
			remainingAmount -= amount;
			RedPacket redPacket = new RedPacket();
			redPacket.setAmount(amount);
			redPackets.add(redPacket);
		}
		distributed = true;
		return redPackets;
	}
	
	/**
	 * Distribute a red packet from remaining amount and number of packets within [min,max]
	 * @param remainingAmount
	 * @param min
	 * @param max
	 * @param remainingCount
	 * @return
	 */
	private int randomRedPacket(int remainingAmount, int min, int max, int remainingCount){
		if(remainingCount == 1){
			return remainingAmount;
		}
		if(min == max){
			return min;
		}
		
		int maxAmount = remainingAmount > max ? max : remainingAmount;
		int randomAmount = (int)Math.rint(betaDistribution.sample() * (maxAmount - min) + min);
		// if the distribution is valid
		if(isValidDistribution(remainingAmount - randomAmount, remainingCount - 1)){
			return randomAmount;
		} else {
			double avg = (remainingAmount - randomAmount) * 1.0 / (remainingCount - 1);
			if(avg < config.getMinimum()){
				return randomRedPacket(remainingAmount, min, randomAmount, remainingCount);  
			} else if(avg > config.getMaximum()){
				return randomRedPacket(remainingAmount, randomAmount, max, remainingCount);
			} else {
				return randomRedPacket(remainingAmount, min, max, remainingCount);
			}
		}
	}

	/**
	 * Check if the distribution could be completed
	 * @param amount
	 * @param numberOfPackets
	 * @return
	 */
	private boolean isValidDistribution(int amount, int numberOfPackets){
		return config.getMinimum() * numberOfPackets <= amount && config.getMaximum() * numberOfPackets >= amount; 
	}
	
}
